/*jslint newcap: true */
/*global XMLHttpRequest: false, FormData: false */
/*
 * Inline Text Attachment
 *
 * Author: Roy van Kaathoven
 * Contact: ik@royvankaathoven.nl
 */
(function (document, window) {
    'use strict';

    var inlineAttachment = function (options, instance) {
        this.settings = inlineAttachment.util.merge(options, inlineAttachment.defaults);
        this.editor = instance;
        this.filenameTag = '{filename}';
        this.lastValue = null;
    };

    /**
     * Will holds the available editors
     *
     * @type {Object}
     */
    inlineAttachment.editors = {};

    /**
     * Utility functions
     */
    inlineAttachment.util = {

        /**
         * Simple function to merge the given objects
         *
         * @param {Object[]} object Multiple object parameters
         * @returns {Object}
         */
        merge: function () {
            var result = {};
            for (var i = arguments.length - 1; i >= 0; i--) {
                var obj = arguments[i];
                for (var k in obj) {
                    if (obj.hasOwnProperty(k)) {
                        result[k] = obj[k];
                    }
                }
            }
            return result;
        },

        /**
         * Append a line of text at the bottom, ensuring there aren't unnecessary newlines
         *
         * @param {String} appended Current content
         * @param {String} previous Value which should be appended after the current content
         */
        appendInItsOwnLine: function (previous, appended) {
            return (previous + "\n\n[[D]]" + appended)
                .replace(/(\n{2,})\[\[D\]\]/, "\n\n")
                .replace(/^(\n*)/, "");
        },

        /**
         * Inserts the given value at the current cursor position of the textarea element
         *
         * @param  {HtmlElement} el
         * @param  {String} value Text which will be inserted at the cursor position
         */
        insertTextAtCursor: function (el, text) {
            var scrollPos = el.scrollTop,
                strPos = 0,
                browser = false,
                range;

            if ((el.selectionStart || el.selectionStart === '0')) {
                browser = "ff";
            } else if (document.selection) {
                browser = "ie";
            }

            if (browser === "ie") {
                el.focus();
                range = document.selection.createRange();
                range.moveStart('character', -el.value.length);
                strPos = range.text.length;
            } else if (browser === "ff") {
                strPos = el.selectionStart;
            }

            var front = (el.value).substring(0, strPos);
            var back = (el.value).substring(strPos, el.value.length);
            el.value = front + text + back;
            strPos = strPos + text.length;
            if (browser === "ie") {
                el.focus();
                range = document.selection.createRange();
                range.moveStart('character', -el.value.length);
                range.moveStart('character', strPos);
                range.moveEnd('character', 0);
                range.select();
            } else if (browser === "ff") {
                el.selectionStart = strPos;
                el.selectionEnd = strPos;
                el.focus();
            }
            el.scrollTop = scrollPos;
        }
    };

    /**
     * Default configuration options
     *
     * @type {Object}
     */
    inlineAttachment.defaults = {
        /**
         * URL where the file will be send
         */
        uploadUrl: 'upload_attachment.php',

        /**
         * Which method will be used to send the file to the upload URL
         */
        uploadMethod: 'POST',

        /**
         * Name in which the file will be placed
         */
        uploadFieldName: 'file',

        /**
         * Extension which will be used when a file extension could not
         * be detected
         */
        defaultExtension: 'png',

        /**
         * JSON field which refers to the uploaded file URL
         */
        jsonFieldName: 'filename',

        /**
         * Allowed MIME types
         */
        allowedTypes: [
            'image/jpeg',
            'image/png',
            'image/jpg',
            'image/gif'
        ],

        /**
         * Text which will be inserted when dropping or pasting a file.
         * Acts as a placeholder which will be replaced when the file is done with uploading
         */
        progressText: '![Uploading file...]()',

        /**
         * When a file has successfully been uploaded the progressText
         * will be replaced by the urlText, the {filename} tag will be replaced
         * by the filename that has been returned by the server
         */
        urlText: "![file]({filename})",

        /**
         * Text which will be used when uploading has failed
         */
        errorText: "Error uploading file",

        /**
         * Extra parameters which will be send when uploading a file
         */
        extraParams: {},

        /**
         * Extra headers which will be send when uploading a file
         */
        extraHeaders: {},

        /**
         * Before the file is send
         */
        beforeFileUpload: function () {
            return true;
        },

        /**
         * Triggers when a file is dropped or pasted
         */
        onFileReceived: function () {
        },

        /**
         * Custom upload handler
         *
         * @return {Boolean} when false is returned it will prevent default upload behavior
         */
        onFileUploadResponse: function () {
            return true;
        },

        /**
         * Custom error handler. Runs after removing the placeholder text and before the alert().
         * Return false from this function to prevent the alert dialog.
         *
         * @return {Boolean} when false is returned it will prevent default error behavior
         */
        onFileUploadError: function () {
            return true;
        },

        /**
         * When a file has succesfully been uploaded
         */
        onFileUploaded: function () {
        }
    };

    /**
     * Uploads the blob
     *
     * @param  {Blob} file blob data received from event.dataTransfer object
     * @return {XMLHttpRequest} request object which sends the file
     */
    inlineAttachment.prototype.uploadFile = function (file) {
        var me = this,
            formData = new FormData(),
            xhr = new XMLHttpRequest(),
            settings = this.settings,
            extension = settings.defaultExtension || settings.defualtExtension;

        if (typeof settings.setupFormData === 'function') {
            settings.setupFormData(formData, file);
        }

        // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now)
        // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name
        if (file.name) {
            var fileNameMatches = file.name.match(/\.(.+)$/);
            if (fileNameMatches) {
                extension = fileNameMatches[1];
            }
        }

        var remoteFilename = "image-" + Date.now() + "." + extension;
        if (typeof settings.remoteFilename === 'function') {
            remoteFilename = settings.remoteFilename(file);
        }

        formData.append(settings.uploadFieldName, file, remoteFilename);

        // Append the extra parameters to the formdata
        if (typeof settings.extraParams === "object") {
            for (var key in settings.extraParams) {
                if (settings.extraParams.hasOwnProperty(key)) {
                    formData.append(key, settings.extraParams[key]);
                }
            }
        }

        xhr.open('POST', settings.uploadUrl);

        // Add any available extra headers
        if (typeof settings.extraHeaders === "object") {
            for (var header in settings.extraHeaders) {
                if (settings.extraHeaders.hasOwnProperty(header)) {
                    xhr.setRequestHeader(header, settings.extraHeaders[header]);
                }
            }
        }

        xhr.onload = function () {
            // If HTTP status is OK or Created
            if (xhr.status === 200 || xhr.status === 201) {
                me.onFileUploadResponse(xhr);
            } else {
                me.onFileUploadError(xhr);
            }
        };
        if (settings.beforeFileUpload(xhr) !== false) {
            xhr.send(formData);
        }
        return xhr;
    };

    /**
     * Returns if the given file is allowed to handle
     *
     * @param {File} clipboard data file
     */
    inlineAttachment.prototype.isFileAllowed = function (file) {
        if (file.kind === 'string') {
            return false;
        }
        if (this.settings.allowedTypes.indexOf('*') === 0) {
            return true;
        } else {
            return this.settings.allowedTypes.indexOf(file.type) >= 0;
        }
    };

    /**
     * Handles upload response
     *
     * @param  {XMLHttpRequest} xhr
     * @return {Void}
     */
    inlineAttachment.prototype.onFileUploadResponse = function (xhr) {
        if (this.settings.onFileUploadResponse.call(this, xhr) !== false) {
            var result = JSON.parse(xhr.responseText),
                filename = result[this.settings.jsonFieldName];

            if (result && filename) {
                var newValue;
                if (typeof this.settings.urlText === 'function') {
                    newValue = this.settings.urlText.call(this, filename, result);
                } else {
                    newValue = this.settings.urlText.replace(this.filenameTag, filename);
                }
                var text = this.editor.getValue().replace(this.lastValue, newValue);
                this.editor.setValue(text);
                this.settings.onFileUploaded.call(this, filename);
            }
        }
    };


    /**
     * Called when a file has failed to upload
     *
     * @param  {XMLHttpRequest} xhr
     * @return {Void}
     */
    inlineAttachment.prototype.onFileUploadError = function (xhr) {
        if (this.settings.onFileUploadError.call(this, xhr) !== false) {
            var text = this.editor.getValue().replace(this.lastValue, this.settings.errorText);
            this.editor.setValue(text);
        }
    };

    /**
     * Called when a file has been inserted, either by drop or paste
     *
     * @param  {File} file
     * @return {Void}
     */
    inlineAttachment.prototype.onFileInserted = function (file) {
        if (this.settings.onFileReceived.call(this, file) !== false) {
            this.lastValue = this.settings.progressText;
            this.editor.insertValue(this.lastValue);
        }
    };


    /**
     * Called when a paste event occured
     * @param  {Event} e
     * @return {Boolean} if the event was handled
     */
    inlineAttachment.prototype.onPaste = function (e) {
        var result = false,
            clipboardData = e.clipboardData,
            items;

        if (typeof clipboardData === "object") {
            items = clipboardData.items || clipboardData.files || [];

          if(!items || items.length <= 0){
            alert("该浏览器不支持操作");
            return;
          }

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (this.isFileAllowed(item)) {
                    result = true;
                    this.onFileInserted(item.getAsFile());
                    this.uploadFile(item.getAsFile());
                }
            }
        }

        if (result) {
            e.preventDefault();
        }

        return result;
    };

    /**
     * Called when a drop event occures
     * @param  {Event} e
     * @return {Boolean} if the event was handled
     */
    inlineAttachment.prototype.onDrop = function (e) {
        var result = false;
        for (var i = 0; i < e.dataTransfer.files.length; i++) {
            var file = e.dataTransfer.files[i];
            if (this.isFileAllowed(file)) {
                result = true;
                this.onFileInserted(file);
                this.uploadFile(file);
            }
        }

        return result;
    };

    window.inlineAttachment = inlineAttachment;

})(document, window);
